/*
moddatetime.c

contrib/spi/moddatetime.c

这是什么？
这是一个从触发器调用的函数，用于在更新记录时更新记录中的修改日期时间戳。

致谢
这95%以上基于autoinc.c，我将其作为起点，因为我实际上并不知道自己在做什么。我还得到了Jan Wieck <jwieck@debis.com>的帮助，他告诉我关于timestamp_in("now")函数的事情。
哦，我是Terry Mackintosh <terry@terrym.com>
*/
#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "utils/builtins.h"
#include "utils/rel.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(moddatetime);

Datum moddatetime(PG_FUNCTION_ARGS)
{
	TriggerData *fc_trigdata = (TriggerData *) fcinfo->context;
	Trigger    *fc_trigger;		/* 获取触发器名称 */
	int			fc_nargs;			/* 参数数量 */
	int			fc_attnum;			/* 要改变的字段的位置编号 */
	Oid			fc_atttypid;		/* 要改变的字段的类型OID */
	Datum		fc_newdt;			/* 当前日期时间。 */
	bool		fc_newdtnull;		/* 其空标志 */
	char	  **fc_args;			/* 参数 */
	char	   *fc_relname;		/* 触发的关系名称 */
	Relation	fc_rel;			/* 触发的关系 */
	HeapTuple	fc_rettuple = NULL;
	TupleDesc	fc_tupdesc;		/* 元组描述 */

	if (!CALLED_AS_TRIGGER(fcinfo))
		/* 内部错误 */
		elog(ERROR, "moddatetime: not fired by trigger manager");

	if (!TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
		/* 内部错误 */
		elog(ERROR, "moddatetime: must be fired for row");

	if (!TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event))
		/* 内部错误 */
		elog(ERROR, "moddatetime: must be fired before event");

	if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
		/* 内部错误 */
		elog(ERROR, "moddatetime: cannot process INSERT events");
	else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
		fc_rettuple = fc_trigdata->tg_newtuple;
	else
		/* 内部错误 */
		elog(ERROR, "moddatetime: cannot process DELETE events");

	fc_rel = fc_trigdata->tg_relation;
	fc_relname = SPI_getrelname(fc_rel);

	fc_trigger = fc_trigdata->tg_trigger;

	fc_nargs = fc_trigger->tgnargs;

	if (fc_nargs != 1)
		/* 内部错误 */
		elog(ERROR, "moddatetime (%s): A single argument was expected", fc_relname);

	fc_args = fc_trigger->tgargs;
	/* 必须是字段布局？ */
	fc_tupdesc = fc_rel->rd_att;

	/*
	 * 这获取我们想要的字段在元组中的位置。args[0]是要更新的字段名，
	 * 从触发器传递过来。
	 */
	fc_attnum = SPI_fnumber(fc_tupdesc, fc_args[0]);

	/*
	 * 在这里我们检查一下我们要更新的字段是否存在。
	 */
	if (fc_attnum <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
				 errmsg("\"%s\" has no attribute \"%s\"",
						fc_relname, fc_args[0])));

	/*
	 * 检查目标字段是否具有允许的类型，并将当前
	 * 日期时间作为该类型的值获取。
	 */
	fc_atttypid = SPI_gettypeid(fc_tupdesc, fc_attnum);
	if (fc_atttypid == TIMESTAMPOID)
		fc_newdt = DirectFunctionCall3(timestamp_in,
									CStringGetDatum("now"),
									ObjectIdGetDatum(InvalidOid),
									Int32GetDatum(-1));
	else if (fc_atttypid == TIMESTAMPTZOID)
		fc_newdt = DirectFunctionCall3(timestamptz_in,
									CStringGetDatum("now"),
									ObjectIdGetDatum(InvalidOid),
									Int32GetDatum(-1));
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
				 errmsg("attribute \"%s\" of \"%s\" must be type TIMESTAMP or TIMESTAMPTZ",
						fc_args[0], fc_relname)));
		fc_newdt = (Datum) 0;		/* 保持编译器安静 */
	}
	fc_newdtnull = false;

	/* 用newdt替换attnum'列 */
	fc_rettuple = heap_modify_tuple_by_cols(fc_rettuple, fc_tupdesc,
										 1, &fc_attnum, &fc_newdt, &fc_newdtnull);

	/* 清理 */
	pfree(fc_relname);

	return PointerGetDatum(fc_rettuple);
}
